package com.github.rxyor.example.transaction.goods.business.impl;

import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.github.rxyor.example.transaction.goods.business.GoodsBusiness;
import com.github.rxyor.example.transaction.goods.mapper.GoodsMapper;
import com.github.rxyor.example.transaction.goods.model.entity.GoodsDO;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import lombok.AllArgsConstructor;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.TransactionIsolationLevel;
import org.springframework.stereotype.Service;

/**
 *<p>
 *
 *</p>
 *
 * @author liuyang
 * @date 2019/9/4 周三 18:00:00
 * @since 1.0.0
 */
@AllArgsConstructor
@Service("goodsBusiness")
public class GoodsBusinessImpl implements GoodsBusiness {

    private final static String LINE = "----------------------------------------";

    private final static String OPERATOR_A = "操作员A:";
    private final static String OPERATOR_B = "操作员B:";

    private final MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean;
    private final GoodsMapper goodsMapper;

    @Override
    public GoodsDO risePriceByReadUncommitted(Long id, BigDecimal floatedPrice) {
        SqlSessionFactory sqlSessionFactory = this.sqlSessionFactory();
        //设置事务隔离级别为读未提交
        SqlSession sqlSessionB = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_UNCOMMITTED);
        SqlSession sqlSessionA = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_UNCOMMITTED);

        GoodsMapper goodsMapperB = sqlSessionB.getMapper(GoodsMapper.class);
        GoodsMapper goodsMapperA = sqlSessionA.getMapper(GoodsMapper.class);

        startOperate(OPERATOR_B);
        selectById(goodsMapperB, id, OPERATOR_B);
        risePrice(goodsMapperB, id, floatedPrice, OPERATOR_B);
        selectById(goodsMapperB, id, OPERATOR_B);
        pauseOperate(OPERATOR_B);

        startOperate(OPERATOR_A);
        selectById(goodsMapperA, id, OPERATOR_A);
        pauseOperate(OPERATOR_A);

        clearCache(sqlSessionB);
        restartOperate(OPERATOR_B);
        rollback(sqlSessionB, OPERATOR_B);
        selectById(goodsMapperB, id, OPERATOR_B);
        stopOperator(OPERATOR_B);

        clearCache(sqlSessionA);
        restartOperate(OPERATOR_A);
        selectById(goodsMapperA, id, OPERATOR_A);
        stopOperator(OPERATOR_A);
        return null;
    }

    @Override
    public GoodsDO risePriceByReadCommitted(Long id, BigDecimal floatedPrice) {
        SqlSessionFactory sqlSessionFactory = this.sqlSessionFactory();
        SqlSession sqlSessionA = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);
        SqlSession sqlSessionB = sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);

        GoodsMapper goodsMapperB = sqlSessionB.getMapper(GoodsMapper.class);
        GoodsMapper goodsMapperA = sqlSessionA.getMapper(GoodsMapper.class);

        startOperate(OPERATOR_B);
        selectById(goodsMapperB, id, OPERATOR_B);
        risePrice(goodsMapperB, id, floatedPrice, OPERATOR_B);
        selectById(goodsMapperB, id, OPERATOR_B);
        pauseOperate(OPERATOR_B);

        startOperate(OPERATOR_A);
        selectById(goodsMapperA, id, OPERATOR_A);
        pauseOperate(OPERATOR_A);

        clearCache(sqlSessionB);
        restartOperate(OPERATOR_B);
        commit(sqlSessionB, OPERATOR_B);
        selectById(goodsMapperB, id, OPERATOR_B);
        stopOperator(OPERATOR_B);

        clearCache(sqlSessionA);
        restartOperate(OPERATOR_A);
        selectById(goodsMapperA, id, OPERATOR_A);
        stopOperator(OPERATOR_A);
        return null;
    }

    private void clearCache(SqlSession sqlSession) {
        System.out.println("清除MyBatis SqlSession缓存");
        sqlSession.clearCache();
    }

    private void startOperate(String operator) {
        System.out.println(LINE);
        System.out.println(operator + ", 开始操作: ");
    }

    private void pauseOperate(String operator) {
        System.out.println(operator + ", 暂停操作: ");
        System.out.println(LINE);
    }

    private void restartOperate(String operator) {
        System.out.println(LINE);
        System.out.println(operator + ", 重新开始操作: ");
    }

    private void stopOperator(String operator) {
        System.out.println(operator + ", 终止操作: ");
        System.out.println(LINE);
    }

    private void selectById(GoodsMapper goodsMapper, Long id, String operator) {
        System.out.println(operator + ", 查询结果: ");
        System.out.println(operator + ", " + goodsMapper.selectById(id));
    }

    private void risePrice(GoodsMapper goodsMapper, Long id, BigDecimal floatedPrice, String operator) {
        System.out.println(operator + ", 涨价: " + floatedPrice.toString() + "元");
        System.out.println(operator + ", " + goodsMapper.risePriceById(id, floatedPrice));
    }

    private void commit(SqlSession sqlSession, String operator) {
        System.out.println(operator + ", 提交事务: ");
        sqlSession.commit();
    }

    private void rollback(SqlSession sqlSession, String operator) {
        System.out.println(operator + ", 回滚事务: ");
        sqlSession.rollback(true);
    }

    private SqlSessionFactory sqlSessionFactory() {
        try {
            return this.mybatisSqlSessionFactoryBean.getObject();
        } catch (Exception e) {
            throw new NullPointerException("sqlSessionFactory is null");
        }
    }

    public static void sleep(Long seconds) {
        if (seconds != null && seconds > 0) {
            try {
                TimeUnit.SECONDS.sleep(seconds);
            } catch (InterruptedException e) {
            }
        }
    }

    @Override
    public GoodsDO subtractStockById(Long id) {
        ExecutorService executorService = Executors.newFixedThreadPool(12);
        SqlSessionFactory sqlSessionFactory = this.sqlSessionFactory();
        GoodsDO goodsDO = null;
        System.out.println(goodsDO);
        for (int i = 0; i < 10000; i++) {
            executorService.submit(() -> {
                SqlSession sqlSession = sqlSessionFactory.openSession();
                GoodsMapper goodsMapper = sqlSession.getMapper(GoodsMapper.class);
                goodsMapper.subtractStockById(1L);
                sqlSession.commit();
            });
        }
        return goodsDO;
    }

    @Override
    public void highConcurrencyUpdate(int threads) {
        if (threads <= 0) {
            threads = 2;
        }
        ExecutorService executorService = new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(threads));
        HashMap<Integer, Future> map = new HashMap<>();

        CountDownLatch countDownLatch = new CountDownLatch(threads);
        for (int i = 0; i < threads; i++) {
            Future future = executorService.submit(() -> {
                try {
                    goodsMapper.risePriceById(1L, new BigDecimal("1"));
                } finally {
                    countDownLatch.countDown();
                }
            });
            map.put(i, future);
        }

        boolean loop = true;
        while (loop) {
            try {
                countDownLatch.await();
                break;
            } catch (Exception e) {
                loop = false;
            }
        }
    }

    public static boolean futureGet(Future future) {
        if (future != null) {
            try {
                future.get();
                return true;
            } catch (Exception e) {
            }
        }
        return false;
    }
}
